﻿using Beaker.Services;
using Beaker.Services.Interfaces;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using Beaker.Services.Locator;

namespace Beaker.OpenCube
{
	/// <summary>
	/// Dynamicly processes any files needed to access the save games
	/// </summary>
	public sealed class DynamicGame
	{
		private readonly IServiceLocator _serviceLocator;
		private readonly IIOService _ioService;

		/// <summary>
		/// Initializes a new instance of the <see cref="DynamicGame"/> class.
		/// </summary>
		public DynamicGame()
			: this( null, null )
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="DynamicGame"/> class.
		/// </summary>
		/// <param name="serviceLocator">The service locator that provides the services, or <c>null</c> to find it by <see cref="ServiceLocator.Current"/>, or auto initalize using a <see cref="PreInitializedServiceLocator"/>.</param>
		public DynamicGame( IServiceLocator serviceLocator )
			: this( serviceLocator, null )
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="DynamicGame"/> class.
		/// </summary>
		/// <param name="rootGameFolder">The root game folder, or <c>null</c> to use the default game path.</param>
		public DynamicGame( string rootGameFolder )
			: this( null, rootGameFolder )
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="DynamicGame"/> class.
		/// </summary>
		/// <param name="serviceLocator">The service locator that provides the services, or <c>null</c> to find it by <see cref="ServiceLocator.Current"/>, or auto initalize using a <see cref="PreInitializedServiceLocator"/>.</param>
		/// <param name="rootGameFolder">The root game folder, or <c>null</c> to use the default game path.</param>
		/// <remarks>
		/// Requires the following services:
		/// * <see cref="IIOService"/> for accessing the files and folders of the game files.
		/// </remarks>
		public DynamicGame( IServiceLocator serviceLocator, string rootGameFolder )
		{
			_serviceLocator = serviceLocator ?? ServiceLocator.Current ?? PreInitializedServiceLocator.Initialize();
			_ioService = _serviceLocator.GetInstance<IIOService>();

			if( _ioService == null )
				throw new ArgumentException( "The service locator did not provided an implementation for IIOService", "serviceLocator" );

			if( string.IsNullOrEmpty( rootGameFolder ) )
				rootGameFolder =
					Path.Combine(
						_ioService.GetSpecialFolderPath( Environment.SpecialFolder.ApplicationData ),
						".minecraft"
					);

			_rootGameFolder = rootGameFolder;
		}

		[ContractInvariantMethod]
		private void ContractInvariant()
		{
			Contract.Invariant( !string.IsNullOrEmpty( _rootGameFolder ) );
			Contract.Invariant( _serviceLocator != null );
			Contract.Invariant( _ioService != null );
		}

		private readonly string _rootGameFolder;

		/// <summary>
		/// Gets the root game folder the dynamic game reader will use.
		/// </summary>
		/// <value>
		/// The root game folder.
		/// </value>
		public string RootGameFolder
		{
			get
			{
				Contract.Ensures( !string.IsNullOrEmpty( Contract.Result<string>() ) );
				return _rootGameFolder;
			}
		}

		/// <summary>
		/// Returns the texture-pack file names found in the texture-pack folder.
		/// </summary>
		/// <returns>Sequence of all texture-pack file names found in the texture-pack folder.</returns>
		public IEnumerable<string> GetTexturePackNames()
		{
			Contract.Ensures( Contract.Result<IEnumerable<string>>() != null );

			string texturePackFolder = Path.Combine( _rootGameFolder, "texturepacks" );
			var zipFiles = _ioService.GetFiles( texturePackFolder, "*.zip" );

			return from zipFile in zipFiles
				   select Path.GetFileName( zipFile );
		}

		/// <summary>
		/// Opens the texture pack with the given file name.
		/// </summary>
		/// <param name="texturePackFileName">Name of the texture pack file.</param>
		/// <returns>
		/// a <see cref="DynamicTexturePack" /> to read texture pack information.
		/// </returns>
		/// <exception cref="System.IO.FileNotFoundException">Thrown when the texture pack file was not found</exception>
		public DynamicTexturePack OpenTexturePack( string texturePackFileName )
		{
			Contract.Requires<ArgumentNullException>( !string.IsNullOrEmpty( texturePackFileName ) );
			Contract.Ensures( Contract.Result<DynamicTexturePack>() != null );

			string texturePackPath = Path.Combine( _rootGameFolder, "texturepacks", texturePackFileName );

			if( !File.Exists( texturePackPath ) )
				throw new FileNotFoundException( texturePackPath );

			return new DynamicTexturePack( _serviceLocator, texturePackFileName );
		}

		/// <summary>
		/// Opens the default texture pack
		/// </summary>
		/// <returns>The default texture pack</returns>
		public DynamicTexturePack OpenDefaultTexturePack()
		{
			string texturePackPath = Path.Combine( _rootGameFolder, "bin", "minecraft.jar" );
			return new DynamicTexturePack( _serviceLocator, texturePackPath );
		}

		/// <summary>
		/// Returns the world names found in the saves folder.
		/// </summary>
		/// <returns>Sequence of all world names found in the saves folder.</returns>
		public IEnumerable<string> GetWorldNames()
		{
			Contract.Ensures( Contract.Result<IEnumerable<string>>() != null );

			string saveFolder = Path.Combine( _rootGameFolder, "saves" );
			var folders = _ioService.GetFolders( saveFolder );

			return from folder in folders
				   where _ioService.FileExists( Path.Combine( folder, "level.dat" ) )
				   select Path.GetFileName( folder );
		}

		/// <summary>
		/// Opens the level with the specified name.
		/// </summary>
		/// <param name="worldName">Name of the level to open.</param>
		/// <returns>a <see cref="DynamicWorld"/> to read game level information.</returns>
		public DynamicWorld OpenWorld( string worldName )
		{
			Contract.Requires<ArgumentOutOfRangeException>( !string.IsNullOrEmpty( worldName ) );
			Contract.Ensures( Contract.Result<DynamicWorld>() != null );

			string saveFolder = Path.Combine( _rootGameFolder, "saves" );
			string gameFolder = Path.Combine( saveFolder, worldName );

			return new DynamicWorld( _serviceLocator, this, gameFolder );
		}
	}
}
